package net.oschina.allchat.server.framework.pipe.stream.negotiants;

import java.util.List;

import net.oschina.allchat.message.processing.IProcessingFactory;
import net.oschina.allchat.message.processing.ProcessingService;
import net.oschina.allchat.message.processing.annotation.AnnotatedParserFactory;
import net.oschina.allchat.message.processing.parsers.core.stream.tls.StartTlsParser;
import net.oschina.allchat.message.processing.translators.SimpleObjectTranslatorFactory;
import net.oschina.allchat.protocol.core.ProtocolChain;
import net.oschina.allchat.protocol.core.ProtocolException;
import net.oschina.allchat.protocol.core.stream.Feature;
import net.oschina.allchat.protocol.core.stream.Stream;
import net.oschina.allchat.protocol.core.stream.error.NotAuthorized;
import net.oschina.allchat.protocol.core.stream.tls.Failure;
import net.oschina.allchat.protocol.core.stream.tls.Proceed;
import net.oschina.allchat.protocol.core.stream.tls.StartTls;
import net.oschina.allchat.server.framework.core.integration.IMessage;
import net.oschina.allchat.server.framework.pipe.stream.IClientConnectionContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TlsNegotiant extends InitialStreamNegotiant {
	private static final Logger logger = LoggerFactory.getLogger(TlsNegotiant.class);
	
	private static IProcessingFactory processingFactory = ProcessingService.createStreamProcessingFactory();
	
	static {
		processingFactory.register(ProtocolChain.first(StartTls.PROTOCOL),
				new AnnotatedParserFactory<StartTls>(StartTlsParser.class));
		
		processingFactory.register(Proceed.class,
				new SimpleObjectTranslatorFactory<Proceed>(
						Proceed.class,
						Proceed.PROTOCOL)
				);
		processingFactory.register(Failure.class,
				new SimpleObjectTranslatorFactory<Failure>(
						Failure.class,
						Failure.PROTOCOL)
				);
		
	}
	
	private enum TlsNegotiationState {
		NONE,
		PROCEED_RESPONSE_SENT,
		TLS_ESTABLISHED
	};
	
	private boolean tlsRequired;
	private TlsNegotiationState state;
	
	public TlsNegotiant(String domainName, boolean tlsRequired, List<Feature> features) {
		super(domainName, features);
		
		this.tlsRequired = tlsRequired;
		state = TlsNegotiationState.NONE;
	}

	@Override
	protected boolean doNegotiate(IClientConnectionContext context, IMessage message) {
		Object request = null;
		try {
			request = processingFactory.parse((String)message.getPayload());
		} catch (ProtocolException e) {
			if (tlsRequired) {
				throw e;
			} else {
				if (next != null) {
					done = true;
					return next.negotiate(context, message);
				} else {
					logger.warn("is tls negotiant the last negotiant???");
					throw new RuntimeException("is tls negotiant the last negotiant???");
				}
			}
		}
		
		if (state != TlsNegotiationState.TLS_ESTABLISHED && isTlsStarted(context)) {
			state = TlsNegotiationState.TLS_ESTABLISHED;
		}
		
		if (state == TlsNegotiationState.NONE) {
			if (request instanceof StartTls) {
				processStartTlsRequest(context);
				return false;
			} else {
				throw new ProtocolException(new NotAuthorized());
			}			
		} else {
			return super.doNegotiate(context, message);
		}
	}

	protected boolean isTlsStarted(IClientConnectionContext context) {
		return context.isTlsSupported() && context.isTlsStarted();
	}

	private void processStartTlsRequest(IClientConnectionContext context) {
		if (!context.isTlsSupported()) {
			processFailure(context);
			return;
		}
		
		startTls(context);
		context.write(processingFactory.translate(new Proceed()));
		state = TlsNegotiationState.PROCEED_RESPONSE_SENT;
	}

	private void processFailure(IClientConnectionContext context) {
		Failure failure = new Failure();
		context.write(processingFactory.translate(failure));
		
		context.write(processingFactory.translate(new Stream(true)));
		
		context.close(true);
	}

	protected void startTls(IClientConnectionContext context) {
		try {
			context.startTls();
		} catch (SecurityException e) {
			processFailure(context);
			logger.error("can't start tls", e);
		}
	}

}
